using System;
using System.Collections;
using System.IO;
using System.Text;
using dnAnalytics.Exceptions;
using dnAnalytics.LinearAlgebra;
using dnAnalytics.LinearAlgebra.IO;
using dnAnalytics.LinearAlgebra.Solvers;
using dnAnalytics.LinearAlgebra.Solvers.Preconditioners;

namespace dnA.Math.MatrixConsole
{
    /// <summary>
    /// Summary description for Class1.
    /// </summary>
    internal class EntryPoint
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        private static void Main(string[] args)
        {
            // See if we have arguments. Should be the name of a
            // directory containing matrix files
            if (args.Length != 1)
            {
                // Should we print some message here????
                Console.WriteLine("Usage: IterationTestRunner.exe ConfigFile.xml");
                return;
            }
            
            // Open the config file
            
            // Get the list of solvers
            // Get the list of preconditioners
            

            DirectoryInfo directory = CreateDirectoryInfo(args[0]);
            // Get the files in the directory. 
            // Add all mtx files to a list
            FileInfo[] matrixFiles = GetMatrixFiles(directory);

            DoSomething(directory, matrixFiles);
        }

        private static void DoSomething(DirectoryInfo directory, FileInfo[] matrixFiles)
        {
            // create the preconditioner list
            IPreconditioner[] preconditioners = CreatePreconditionerCollection();

            // create the solver list
            IIterativeMatrixSolver[] solvers = CreateSolverCollection();

            // create the iteration monitor
            MatrixConvergenceMonitor monitor = new MatrixConvergenceMonitor(3500, 1e-10);

            // create the file stream that will hold the results
            string file = Path.Combine(directory.FullName, "results.txt");
            StreamWriter writer = new StreamWriter(file, false);
            try
            {
                WriteFileHeader(writer);
                foreach (IIterativeMatrixSolver matrixSolver in solvers)
                {
                    foreach (IPreconditioner preconditioner in preconditioners)
                    {
                        // for each file run the matrix test and report the 
                        // results
                        foreach (FileInfo matrixFile in matrixFiles)
                        {
                            // Write the first part of the line, containing
                            // MatrixName   SolverType  PreconditionerType
                            StringBuilder builder = new StringBuilder();
                            builder.Append(matrixFile.Name);
                            builder.Append("\t");
                            builder.Append(matrixSolver.GetType().Name);
                            builder.Append("\t");
                            builder.Append(preconditioner.GetType().Name);
                            builder.Append("\t");

                            // Read the file and create the matrix
                            Matrix matrix = null;
                            try
                            {
                                MatrixMarketReader reader = new MatrixMarketReader();
                                matrix = reader.ReadMatrix(matrixFile.FullName, MatrixType.Sparse);
                            }
                            catch (ArgumentNullException e)
                            {
                                Console.Write(e);
                            }
                            catch (IOException e)
                            {
                                Console.Write(e);
                            }

                            if (matrix == null)
                            {
                                // state that creation of matrix failed
                                builder.Append("Matrix creation failed");
                                builder.Append(Environment.NewLine);
                                writer.Write(builder.ToString());
                                continue;
                            }

                            // create the right hand side vector
                            // For the test the vector will be filled with 1's
                            Vector rightHandSideVector = VectorBuilder.CreateVector(matrix.Rows, 1.0);

                            // Create the solver
                            monitor.Clear();

                            // Setup the preconditioner
                            preconditioner.OriginalMatrix = matrix;

                            // Setup the matrix solver
                            matrixSolver.Preconditioner = preconditioner;
                            matrixSolver.ConvergenceMonitor = monitor;
                            matrixSolver.Matrix = matrix;

                            DoubleVector result;
                            try
                            {
                                Console.Write("Solving for matrix " + matrixFile.Name + " with " + matrixSolver.GetType().Name + " & " + preconditioner.GetType().Name);
                                Console.Write(Environment.NewLine);
                                result = matrixSolver.Solve(rightHandSideVector);
                            }
                            catch (BiConjugateGradientBrakedownException)
                            {
                                // bombed out, no convergence
                                builder.Append("Solver break down");
                                builder.Append(Environment.NewLine);
                                writer.Write(builder.ToString());
                                continue;
                            }
                            catch (ArgumentOutOfRangeException)
                            {
                                builder.Append("NaN detected");
                                builder.Append(Environment.NewLine);
                                writer.Write(builder.ToString());
                                continue;
                            }

                            double[] results = monitor.Results();
                            // see if it converged
                            if ((int)results[0] >= 3500)
                            {
                                builder.Append("not converged");
                            }
                            else
                            {
                                if (System.Math.Abs(results[1]) >= System.Math.Abs(results[2]))
                                {
                                    builder.Append("converged");
                                }
                                else
                                {
                                    // we didn't get an exception but it didn't converge
                                    // either. This is due to the convergence monitor 
                                    // stopping the iteration due to NaN's
                                    builder.Append("Divergence detected");
                                }
                            }

                            double difference = 0.0;
                            if (result != null)
                            {
                                // Check accuracy of result
                                DoubleVector residual = new DoubleVector(result.Count);
                                // calculate residual = Ax - b
                                matrix.Multiply(result, residual);
                                residual.Subtract(rightHandSideVector);

                                // Find the largest difference
                                for (int i = 0; i < residual.Count; i++)
                                {
                                    if (System.Math.Abs(residual[i]) > difference)
                                    {
                                        difference = System.Math.Abs(residual[i]);
                                    }
                                }
                            }

                            builder.Append("\t");
                            // write the iteration count
                            builder.Append((int)results[0]);
                            builder.Append("\t");
                            // write the residual boundary
                            builder.Append(results[1]);
                            builder.Append("\t");
                            // write the residual
                            builder.Append(results[2]);
                            builder.Append("\t");
                            // write real residual
                            builder.Append(difference);
                            builder.Append(Environment.NewLine);
                            writer.Write(builder.ToString());
                        }
                    }
                }
            }
            finally
            {
                writer.Flush();
                writer.Close();
            }
        }

        private static void WriteFileHeader(StreamWriter writer)
        {
            StringBuilder header = new StringBuilder();
            // Write the file header. 
            header.Append("MatrixName\tsolver type\tpreconditioner type\tconvergence\titeration count\tconvergence boundary\tcalculated residual\treal residual");
            header.Append(Environment.NewLine);
            writer.Write(header.ToString());
        }

        private static DirectoryInfo CreateDirectoryInfo(string directoryName)
        {
            // parse the directory
            DirectoryInfo directory = null;
            try
            {
                directory = new DirectoryInfo(directoryName);
            }
            catch (ArgumentNullException e)
            {
                Console.Write(e);
            }
            catch (ArgumentException e)
            {
                Console.Write(e);
            }
            return directory;
        }

        private static FileInfo[] GetMatrixFiles(DirectoryInfo directory)
        {
            ArrayList files = new ArrayList();

            FileInfo[] foundFiles = directory.GetFiles();
            foreach (FileInfo foundFile in foundFiles)
            {
                if (foundFile.Extension.ToLower() == ".mtx")
                {
                    files.Add(foundFile);
                }
            }

            FileInfo[] result = new FileInfo[files.Count];
            files.CopyTo(result);
            return result;
        }

        private static IPreconditioner[] CreatePreconditionerCollection()
        {
            IPreconditioner[] result = new IPreconditioner[1];
            //result[0] = new UnitPreconditioner();
            //result[1] = new DiagonalPreconditioner();
            result[0] = new DiagonalIncompleteLUFactorization();
            //result[3] = new IncompleteLUFactorization();
            //result[4] = new IlutpPreconditioner();
            return result;
        }

        private static IIterativeMatrixSolver[] CreateSolverCollection()
        {
            IIterativeMatrixSolver[] result = new IIterativeMatrixSolver[1];
            result[0] = new BiConjugateGradientStabilizedSolver();
            return result;
        }
    }
}